#include "MediaInspector.h"
#include <QDir>
#include <QFileInfo>
#include <QThread>
#include "Decoder.h"
#include <QCryptographicHash>
#include <QStandardPaths>
#include <QImage>

MediaInspector::MediaInspector(QObject* parent)
{
    auto titleImgDir = QDir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation));
    if(!titleImgDir.exists("titleImages")) {
        titleImgDir.mkdir("titleImages");
    }

    m_titleImgRootPath = titleImgDir.absoluteFilePath("titleImages");
}

int MediaInspector::openDir(const QString &path)
{

// 通过编译宏判断是否是ARMv7，也就是STM32MP1，如果是，则不执行视频列表扫描，函数直接结束。
#if defined(OS_STM32MP1)
    m_stateDesc = "ARMv7 platform, not support video list scan.";
    qDebug() << m_stateDesc;
    return -1;
#endif

    auto newDir = QDir(path);
    if(!newDir.exists()) {
        m_stateDesc = "Directory does not exist";
        return -1;
    }

    if(newDir == m_dir) {
        return 1;
    }

    m_dir = newDir;

    beginResetModel();
    m_files.clear();
    endResetModel();

    QThread* td = QThread::create(&MediaInspector::openDirAsync, this);

    connect(td, &QThread::finished, this, [this, td](){
        emit scanFinished();
        td->deleteLater();
    });
    td->start();

    return true;
}

int MediaInspector::count()
{
    return m_files.size();
}

int MediaInspector::rowCount(const QModelIndex &parent) const
{
    return m_files.size();
}

QVariant MediaInspector::data(const QModelIndex &index, int role) const
{
    if(index.row() < 0 || index.row() >= m_files.size()) {
        return QVariant();
    }

    const auto& model = m_files[index.row()];

    switch(role) {
    case NameRole:
        return model.name;
    case TitleImagePathRole:
        return model.titleImagePath;
    case LastTimepointRole:
        return model.lastTimepoint;
    case DurationRole:
        return model.duration;
    case FilenameRole:
        return model.filename;
    default:
        break;
    }

    return QVariant();
}

void MediaInspector::setTitleImgDir(QUrl url)
{
    if(!url.isLocalFile()) {
        qWarning() << "The url is not a local path.";
        return;
    }

    auto path = url.toLocalFile();
    QDir dir(path);
    if(!dir.exists()) {
        qWarning() << "Not found path.";
        return;
    }

    m_titleImgRootPath = path;
}

bool MediaInspector::setData(const QModelIndex &index, const QVariant &value, int role)
{
    if(index.row() < 0 || index.row() >= m_files.size()) {
        return false;
    }

    auto& model = m_files[index.row()];
    switch(role) {


    case TitleImagePathRole:
        model.titleImagePath = value.toString();
        emit dataChanged(index, index, {TitleImagePathRole});
        break;

    case LastTimepointRole:
        model.lastTimepoint = value.toDouble();
        emit dataChanged(index, index, {LastTimepointRole});
        break;

    case NameRole:
    case DurationRole:
    default:
        return false;
    }


    return true;
}

QString MediaInspector::filename(int i)
{
    if(i < 0 || i >= m_files.size())
        return "";

    return m_files[i].filename;
}

Qt::ItemFlags MediaInspector::flags(const QModelIndex &index) const
{
    return QAbstractListModel::flags(index) | Qt::ItemFlags::enum_type::ItemIsEditable;
}

QHash<int, QByteArray> MediaInspector::roleNames() const
{
    QHash<int, QByteArray> roles;
    roles[NameRole] = "name";
    roles[TitleImagePathRole] = "titleImg";
    roles[LastTimepointRole] = "lastTimepoint";
    roles[DurationRole] = "duration";
    roles[FilenameRole] = "filename";
    return roles;
}

void MediaInspector::sort(SortFlag flag)
{
    auto nameSortPred = [](const MediaModel& l, const MediaModel& r){
        return l.name < r.name;
    };

    auto lastModifyPred = [](const MediaModel& l, const MediaModel& r){
        return l.fileLastModifyTime < r.fileLastModifyTime;
    };

    auto lastTimepointPred = [](const MediaModel& l, const MediaModel& r){
        return l.lastTimepoint < r.lastTimepoint;
    };

    std::array<std::function<bool(const MediaModel&, const MediaModel&)>, 3> pred = {nameSortPred, lastModifyPred, lastTimepointPred};

    emit layoutAboutToBeChanged();
    std::sort(m_files.begin(), m_files.end(), pred[flag]);
    emit layoutChanged();
}

void MediaInspector::setLastTimepoint(int i, double sec)
{
    if(i < 0 || i >= m_files.size())
        return ;

    m_files[i].lastTimepoint = sec;

    auto index = createIndex(i, 0);
    emit dataChanged(index, index, {LastTimepointRole});
}

void MediaInspector::openDirAsync()
{
    auto files = m_dir.entryInfoList(QDir::Files | QDir::NoSymLinks, QDir::Name);
    for(int i = 0; i < files.size(); ++i) {
        auto fileInfo = files[i];
        if(fileInfo.isFile()) {
            auto model = validateFile(fileInfo.absoluteFilePath());
            if(model.has_value()) {
                model->fileLastModifyTime = fileInfo.lastModified();
                model->fileBirthTime = fileInfo.birthTime();

                auto modelVal = model.value();

                QMetaObject::invokeMethod(this, [this, modelVal](){
                    beginInsertRows(QModelIndex(), m_files.size(), m_files.size());
                    m_files.push_back(modelVal);
                    endInsertRows();
                }, Qt::QueuedConnection);
            }
        }
    }
}

std::optional<MediaInspector::MediaModel> MediaInspector::validateFile(const QString& filePath)
{
    MediaModel model;
    model.filename = filePath;
    model.name = QFileInfo(filePath).fileName();

    auto md5 = QCryptographicHash::hash(filePath.toUtf8(), QCryptographicHash::Md5).toHex().toUpper();
    QString titleImgName = QString("IMG_%1.png").arg(md5);
    QDir rootDir(m_titleImgRootPath);

    Decoder decoder;
    decoder.setVideoFrameFormat(QSize(90, 90), AV_PIX_FMT_RGBA);
    if(!decoder.start(QUrl::fromLocalFile(filePath))) {
        return std::nullopt;
    }

    model.duration = decoder.fileDuration();

    qDebug() << "file: " << model.name << ", duration: " << model.duration;

    if(!m_titleImgRootPath.isEmpty()) {
        if(rootDir.exists(titleImgName)) {
            model.titleImagePath = rootDir.absoluteFilePath(titleImgName);
            return model;
        }
    }

    double imgPos = 30;
    if(decoder.fileDuration() < 30) {
        imgPos = decoder.fileDuration() / 2.0;
    }
    decoder.seek(imgPos);

    auto frame = decoder.nextVideoFrame(1000);
    if(!frame) {
        qWarning() << "Failed to get video frame, timepoint: " << imgPos << "s";
        return model;
    }

    QImage img(frame->data[0], 90, 90, frame->linesize[0], QImage::Format_RGBA8888);
    if(!img.save(rootDir.absoluteFilePath(titleImgName))) {
        qWarning() << "Cannot save title image to path:" << titleImgName;
    }

    model.titleImagePath = rootDir.absoluteFilePath(titleImgName);
    av_frame_free(&frame);

    return model;
}
